package Request

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	Slice "gitee.com/yebetter/dingding_robot/helper/slice"
	Unique "gitee.com/yebetter/dingding_robot/helper/unique"
	"io/ioutil"
	"net/http"
	"net/http/cookiejar"
	"strings"
	"sync"
	"time"
)

var defaultHeader = map[string]string{
	"Content-Type": "application/json;charset=utf-8",
}

/**
客户端访问请求
@param url string 发送地址
@param data interface{} 发送数据
@param contentType string 数据类型
@param timeout int 超时时间(秒)
*/
type HttpResponse struct {
	StatusCode int    //请求状态
	Content    string //内容
	Cookie     []*http.Cookie
	Header     http.Header
}

type HttpError struct {
	Code  int
	Error error
}

type FnError func(string, *HttpError)
type FnSuccess func(string, *HttpResponse)

const (
	HTTP_ERROR_TIME_OUT = 122 //超时
	HTTP_SUCCESS        = 100 //正常
	HTTP_ERROR          = 101 //错误

	HTTP_TIME_OUT     = 15 //默认超时时间(秒)
	HTTP_MAX_TRY_TIME = 5  //默认最大重试次数
)

type HttpClient struct {
	maxTryTime int //请求最大次数
	tryTime    int //当前尝试次数
	timeOut    int //超时时间(秒)

	identifier string            //请求标识 每次处理请求都唯一
	url        string            //请求地址
	data       interface{}       //请求数据
	header     map[string]string //请求header
	cookie     []*http.Cookie

	errorFun   FnError   //错误回调
	successFun FnSuccess //成功回调

	headerReadLock *sync.RWMutex
}

func getDefaultHeader() map[string]string {
	outHeader := make(map[string]string)

	for k, v := range defaultHeader {
		outHeader[k] = v
	}
	return outHeader
}

/**
防止并发时对全局map  同时写及读
*/
func (h *HttpClient) GetHeader() map[string]string {
	return h.header
}

func (h *HttpClient) AddHeader(setheader map[string]string) {
	h.headerReadLock.Lock()
	for k, v := range setheader {
		h.header[k] = v
	}
	h.headerReadLock.Unlock()
}

/**
自定义超时时间请求
@param timeOut int 超时时间


[使用方法]
httpClict := Request.NewHttpClient()
	headerMap := map[string]string{
				"Content-Type": "application/x-www-form-urlencoded",
			}
	httpClict.AddHeader(headerMap)
//设置传参
	dataParam := make(map[string]string)
	dataParam["notifyIdentifier"] = "21321"
	dataParam["userIdentifier"] = "21321"
	dataParam["extraInfo"] = "21321"
	dataParam["orderId"] = "21321"

	httpClict.Init(url, dataParam)
	httpClict.SetSuccessListener(func(identifier string, response *Request.HttpResponse) {
		//content := response.Content

		//保存推送的订单号
		//V3Cache.CacheStringInstance().SetCache(key.Key+":"+orderId, "1", key.ExpireTime)

	})
	httpClict.SetErrorListener(func(s string, httpError *Request.HttpError) {

	})
	httpClict.HttpPostReTryTimeOut()
*/
func NewHttpClientTimeOut(timeOut int) *HttpClient {
	return &HttpClient{maxTryTime: HTTP_MAX_TRY_TIME, tryTime: 1, timeOut: timeOut, headerReadLock: new(sync.RWMutex), header: getDefaultHeader()}
}

/**
默认请求
*/
func NewHttpClient() *HttpClient {
	return &HttpClient{maxTryTime: HTTP_MAX_TRY_TIME, tryTime: 1, timeOut: HTTP_TIME_OUT, headerReadLock: new(sync.RWMutex), header: getDefaultHeader()}
}

/**
初始化请求
*/
func (h *HttpClient) Init(url string, data interface{}) {
	identifier, err := Unique.GetUniqueNum()
	if err != nil {
		log("生成标识错误")
		return
	}
	h.identifier = identifier
	h.url = url
	h.data = data
	//h.header = header
}

func (h *HttpClient) SetCookie(cookie []*http.Cookie) {
	h.cookie = cookie
}

func (h *HttpClient) SetErrorListener(fn FnError) {
	h.errorFun = fn
}
func (h *HttpClient) SetSuccessListener(fn FnSuccess) {
	h.successFun = fn
}

/**
http 发送带超时回调用get请求
@param httpUrl string 请求地址
@param data interface 请求参数
@param header map[string]interface 头部信息
@param timeout int 超时时间
@param tryTime int 重式次数
@param maxTryTime int 最大重试次数
*/
func (h *HttpClient) HttpPostReTryTimeOut() {
	if len(strings.TrimSpace(h.url)) == 0 {
		errsd := &HttpError{
			Code:  HTTP_ERROR,
			Error: errors.New("request url error empty"),
		}
		if h.errorFun != nil {
			h.errorFun(h.identifier, errsd)
		} else {
			errsd.Error = errors.New("request url error empty")
			h.errorFun(h.identifier, errsd)
		}
		return
	}
	//先发送一次请求
	httpResponse, httpReponseError := HttpPost(h, h.url, h.data, h.header, h.cookie, h.timeOut)
	//requestMsg := fmt.Sprintf("[info]发起请求-%s:[%s],method:[%s] 当前次数:[%d], 参数:[%+v], 头信息:[%+v]", h.identifier, h.url, "POST", h.tryTime, h.data, h.header)
	//log(requestMsg)
	//如果请求错误
	if httpReponseError != nil {
		//请求超时
		switch httpReponseError.Code {
		case HTTP_ERROR_TIME_OUT, HTTP_ERROR: //请求超时
			if h.tryTime >= h.maxTryTime { //超过次数
				requestMsg := fmt.Sprintf("[error]请求超时-%s:[%s], 当前次数:[%d], 参数:[%+v], 头信息:[%+v]", h.identifier, h.url, h.tryTime, h.data, h.header)
				log(requestMsg)
				if h.errorFun != nil {
					h.errorFun(h.identifier, httpReponseError)
				}
				return
			} else {
				time.Sleep(time.Microsecond * 50)
				h.tryTime++
				h.HttpPostReTryTimeOut()
				return
			}
		default: //通用错误
			requestMsg := fmt.Sprintf("[error]请求错误-%s:[%s], 当前次数:[%d], 参数:[%+v], 头信息:[%+v], error:[%+v]", h.identifier, h.url, h.tryTime, h.data, h.header, httpReponseError)
			log(requestMsg)
			h.errorFun(h.identifier, httpReponseError)
			return
		}
	}
	if h.successFun != nil {
		h.successFun(h.identifier, httpResponse)
	}
}

/**
发送带超时回调用get请求
*/
func (h *HttpClient) HttpGetReTryTimeOut() {
	if len(strings.TrimSpace(h.url)) == 0 {
		errsd := &HttpError{
			Code:  HTTP_ERROR,
			Error: errors.New("request url error empty"),
		}
		if h.errorFun != nil {
			h.errorFun(h.identifier, errsd)
		} else {
			fmt.Println("request url error empty")
		}
		return
	}
	//先发送一次请求
	httpResponse, httpReponseError := HttpGet(h, h.url, h.data, h.header, h.cookie, h.timeOut)
	requestMsg := fmt.Sprintf("[info]发起请求-%s:[%s], method:[%s] data:[%+v], 当前次数:[%d],cooke信息:[%+v], 头信息:[%+v]", h.identifier, h.url, "GET", h.data, h.tryTime, h.cookie, h.header)
	log(requestMsg)

	if httpReponseError != nil {
		//请求超时
		switch httpReponseError.Code {
		case HTTP_ERROR_TIME_OUT, HTTP_ERROR: //请求超时
			if h.tryTime >= h.maxTryTime { //超过次数
				requestMsg := fmt.Sprintf("[error]请求超时-%s:[%s], 当前次数:[%d], 参数:[%+v], 头信息:[%+v]", h.identifier, h.url, h.tryTime, h.data, h.header)
				log(requestMsg)
				if h.errorFun != nil {
					h.errorFun(h.identifier, httpReponseError)
				}
				return
			} else {
				time.Sleep(time.Microsecond * 50)
				h.tryTime++
				//h.HttpPostReTryTimeOut()
				h.HttpGetReTryTimeOut()
				return
			}
		default: //通用错误
			requestMsg := fmt.Sprintf("[error]请求错误-%s:[%s], 当前次数:[%d], 参数:[%+v], 头信息:[%+v], error:[%+v]", h.identifier, h.url, h.tryTime, h.data, h.header, httpReponseError)
			log(requestMsg)
			h.errorFun(h.identifier, httpReponseError)
			return
		}
	}
	if h.successFun != nil {
		h.successFun(h.identifier, httpResponse)
	}
}

/**
发送post json
*/
func HttpPostJson(httpUrl string, data []byte, timeOut int) (*HttpResponse, *HttpError) {
	//"application/x-www-form-urlencoded"

	req, err := http.NewRequest("POST", httpUrl, bytes.NewReader(data))
	if err != nil {
		return nil, &HttpError{Code: HTTP_ERROR, Error: err}
	}

	req.Header.Set("Content-Type", "application/json;charset=utf-8")
	//超时处理
	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeOut)*time.Second)
	defer cancel()
	isTimeOut := func() bool {
		select {
		default:
			return false
		case <-ctx.Done():
			// 是否超时
			return errors.Is(ctx.Err(), context.DeadlineExceeded)
		}
	}
	req = req.WithContext(ctx)
	resp, ers := http.DefaultClient.Do(req)

	if ers != nil {
		if isTimeOut() {
			return nil, &HttpError{Code: HTTP_ERROR_TIME_OUT, Error: ers}
		}
		return nil, &HttpError{Code: HTTP_ERROR, Error: ers}
	}
	defer resp.Body.Close()
	result, errs := ioutil.ReadAll(resp.Body)
	if errs != nil {
		return nil, &HttpError{Code: HTTP_ERROR, Error: errs}
	}
	content := string(result)
	httpResponse := HttpResponse{StatusCode: resp.StatusCode, Content: content}
	return &httpResponse, nil
}

/**
发送post请求
@param httpUrl string 请求地址
@param data interface 发送数据
@param header map[string]string 请求头
@param timeOut 超时时间(秒)
*/
func HttpPost(httpClient *HttpClient, httpUrl string, data interface{}, header map[string]string, cookie []*http.Cookie, timeOut int) (*HttpResponse, *HttpError) {
	return httpSend(httpClient, http.MethodPost, httpUrl, data, header, cookie, timeOut)
}

/**
发送get请求
@param httpUrl string 请求地址
@param header map[string]string 请求头
@param timeOut 超时时间(秒)
*/
func HttpGet(httpClient *HttpClient, httpUrl string, data interface{}, header map[string]string, cookie []*http.Cookie, timeOut int) (*HttpResponse, *HttpError) {
	return httpSend(httpClient, http.MethodGet, httpUrl, data, header, cookie, timeOut)
}

func httpSend(httpClient *HttpClient, method string, httpUrl string, data interface{}, header map[string]string, cookie []*http.Cookie, timeOut int) (*HttpResponse, *HttpError) {

	var gCurCookies []*http.Cookie
	gCurCookieJar, _ := cookiejar.New(nil)

	body := ""
	if method == http.MethodPost {
		contentType, ok := header["Content-Type"]
		if ok {
			if hasJson := strings.Index(contentType, "application/json"); hasJson == -1 {
				var dataMap map[string]interface{}
				bytesData, jsErr := json.Marshal(data)
				if jsErr != nil {
					return nil, &HttpError{Code: HTTP_ERROR, Error: jsErr}
				}
				err := json.Unmarshal(bytesData, &dataMap)
				if err != nil {
					return nil, &HttpError{Code: HTTP_ERROR, Error: err}
				}
				body = Slice.MapToUrlString(dataMap)
			} else {
				bytesData, jsErr := json.Marshal(data)
				if jsErr != nil {
					return nil, &HttpError{Code: HTTP_ERROR, Error: jsErr}
				}
				body = string(bytesData)
			}
		} else {
			bytesData, jsErr := json.Marshal(data)
			if jsErr != nil {
				return nil, &HttpError{Code: HTTP_ERROR, Error: jsErr}
			}
			body = string(bytesData)
		}
	}

	req, err := http.NewRequest(method, httpUrl, bytes.NewReader([]byte(body)))
	if err != nil {
		return nil, &HttpError{Code: HTTP_ERROR, Error: err}
	}

	if method == http.MethodGet && data != nil {
		getData := data.(map[string]string)
		q := req.URL.Query()
		for k, v := range getData {
			q.Add(k, v)
		}
		req.URL.RawQuery = q.Encode()
	}

	if header != nil {
		httpClient.headerReadLock.RLock()
		for k, v := range header {
			req.Header.Set(k, v)
		}
		httpClient.headerReadLock.RUnlock()
	}

	if cookie != nil && len(cookie) > 0 {
		for _, v := range cookie {
			req.AddCookie(v)
		}
	}

	//超时处理
	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeOut)*time.Second)
	defer cancel()
	isTimeOut := func() bool {
		select {
		default:
			return false
		case <-ctx.Done():
			// 是否超时
			return errors.Is(ctx.Err(), context.DeadlineExceeded)
		}
	}
	httpClients := &http.Client{
		Jar: gCurCookieJar,
	}
	req = req.WithContext(ctx)
	resp, ers := httpClients.Do(req)

	if ers != nil {
		if isTimeOut() {
			return nil, &HttpError{Code: HTTP_ERROR_TIME_OUT, Error: ers}
		}
		return nil, &HttpError{Code: HTTP_ERROR, Error: ers}
	}

	defer resp.Body.Close()
	gCurCookies = gCurCookieJar.Cookies(req.URL)

	result, errs := ioutil.ReadAll(resp.Body)
	if errs != nil {
		return nil, &HttpError{Code: HTTP_ERROR, Error: errs}
	}
	content := string(result)
	httpResponse := HttpResponse{StatusCode: resp.StatusCode, Cookie: gCurCookies, Header: resp.Header, Content: content}
	return &httpResponse, nil
}

//写入日志
func log(message string, args ...interface{}) {

}
